/*
ont-space - The ontology-based resource metadata repository
Copyright (c) 2006-2008, Information Eng. Research Unit - Univ. of Alcalá
http://www.cc.uah.es/ie
This library is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the Free
Software Foundation; either version 2.1 of the License, or (at your option)
any later version.
This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details.
You should have received a copy of the GNU Lesser General Public License along
with this library; if not, write to the Free Software Foundation, Inc.,
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */
package org.ontspace.owl;

import com.hp.hpl.jena.db.DBConnection;
import com.hp.hpl.jena.db.IDBConnection;
import com.hp.hpl.jena.db.ModelRDB;
import com.hp.hpl.jena.ontology.OntModel;
import com.hp.hpl.jena.ontology.OntModelSpec;
import com.hp.hpl.jena.ontology.Ontology;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.util.FileManager;

import com.mysql.jdbc.Statement;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URI;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.ontspace.MetadataRepository;

import org.ontspace.QueryManager;
import org.ontspace.Session;
import org.ontspace.owl.util.MetadataRepositoryConfigException;
import org.ontspace.owl.util.OntologyNotInRepositoryException;

/**
 * Defines the basic interface of ont-space repositories.
 */
public class MetadataRepositoryImpl implements MetadataRepository {

    public static final String DB_DRIVER_PARAM =
            "OSPACE_REP_DRIVER_PARAM";
    public static final String DB_TYPE_PARAM =
            "OSPACE_TYPE_PARAM";
    public static final String DC_SCHEMA_PATH_PARAM =
            "DC_SCHEMA_PATH_PARAM";
    // database URL
    String M_DB_URL =
            null;
    // User name
    String M_DB_USER =
            null;
    // Password
    String M_DB_PASSWD =
            null;
    // Database engine name
    String M_DB =
            null;
    // JDBC driver
    String M_DBDRIVER_CLASS =
            null;
    URI M_PUBLIC_URI =
            null;
    protected ModelRDB _m;

    /**
     * @param params The params needed to create repository
     * @param create Boolean value to know if the repository has been created 
     * previously
     * @throws org.ont-space.owl.util.MetadataRepositoryConfigException
     */
    MetadataRepositoryImpl(HashMap params, boolean create)
            throws MetadataRepositoryConfigException {


        if (!parameterExists(MetadataRepository.STORAGE_URI_PARAM, params)) {
            throw new MetadataRepositoryConfigException("No URI to storage specified");
        } else {
            M_DB_URL =
                    (String) params.get(MetadataRepository.STORAGE_URI_PARAM);
        }


        M_DB_USER =
                (String) params.get(MetadataRepository.USER_NAME_PARAM);
        M_DB_PASSWD =
                (String) params.get(MetadataRepository.PWD_PARAM);
        M_DBDRIVER_CLASS =
                (String) params.get(MetadataRepositoryImpl.DB_DRIVER_PARAM);
        M_PUBLIC_URI =
                (URI) params.get(MetadataRepositoryImpl.PUBLIC_REP_URI_PARAM);
        M_DB =
                (String) params.get(MetadataRepositoryImpl.DB_TYPE_PARAM);

        try {
            Class.forName(M_DBDRIVER_CLASS);
        } catch (ClassNotFoundException e) {
            System.out.println(e);
        }

        // create a database connection
        IDBConnection conn =
                new DBConnection(M_DB_URL, M_DB_USER, M_DB_PASSWD, M_DB);


        if (!this.existsSchema()) {
            this.createSchema();
        }

        if (create) {
            //es un create
            if (!conn.containsModel("myModelName")) {
                _m =
                        ModelRDB.createModel(conn, "myModelName");
            }
            if (_m == null) {
                throw new MetadataRepositoryConfigException("Could not create model");
            }
            loadSchema((String) params.get(DC_SCHEMA_PATH_PARAM));
            // Additional Ontologies
            boolean exit =
                    false;
            int i =
                    1;
            while (!exit) {
                String _additionalOntology =
                        (String) params.get("DC_ADDITIONAL_PATH_" + i++);
                if (_additionalOntology == null) {
                    exit =
                            true;
                    break;
                } else {
                    loadSchema(_additionalOntology);
                }
            }
        } else {
            _m =
                    ModelRDB.open(conn, "myModelName");
        }

    }

    /**
     * Close the repository and free up resources held
     */
    public void close() {
        try {
            _m.getConnection().close();
        } catch (SQLException ex) {
            Logger.getLogger(MetadataRepositoryImpl.class.getName()).
                    log(Level.SEVERE, null, ex);
        }
        _m.close();
    }

    /**
     * Load the schema with model are stored in a specified path
     * @param path The path where are stored the model
     */
    private void loadSchema(String path) {
        System.out.println(path);
        System.out.println(_m);
        FileManager.get().readModel(_m, path);
    }

    /**
     * Check if exist the parameter
     * @param name The name od the parameter
     * @param params The HashMap params
     * @return Boolean value to indicate if it exist
     */
    private boolean parameterExists(String name, HashMap params) {
        return params.get(name) != null;
    }

    /**
     * Gets a session for the given userId
     * @param userId The user identifier
     * @return The session for a given userId
     */
    public Session getSessionId(String userId) {
        return new SessionImpl(this, userId);
    }

    /**
     * Gets a new anonymous session
     * @return The anonymous session
     */
    public Session getAnonymousSession() {
        return new SessionImpl(this);
    }

    /**
     * Gets the URI of the repository
     * @return The URI of the repository
     */
    public URI getRepositoryURI() {
        return M_PUBLIC_URI;
    }

    /**
     * Gets a new instance of the selected QueryManager. 
     * @param type Specifies the type of QueryMananager
     * @return  A new instance QueryManager specified as type param
     * @throws org.ont-space.owl.util.OntologyNotInRepositoryException
     */
    public QueryManager getQueryManager(String type)
            throws OntologyNotInRepositoryException {
        return getQueryManager(type, null);
    }

    /**
     * Gets a new instance of the selected QueryManager. 
     * @param type Specifies the type of QueryMananager
     * @param params Additional parameters for the QueryManager invocation
     * @return  A new instance QueryManager specified as type param
     * @throws org.ont-space.owl.util.OntologyNotInRepositoryException
     */
    public QueryManager getQueryManager(String type, HashMap params)
            throws OntologyNotInRepositoryException {
        List<URI> uriList =
                new ArrayList<URI>();
        Method requiredOnts, addOnt;
        Object retobj =
                null;

        // Creates the Query Manager

        try {

            // Loads the QueryManager class
            Class qmi =
                    Class.forName(type);


            // Creates the instance of the QueryManager
            Class[] parType;
            Object[] argList;

            // Determines the number of parameters for the constructor
            if (params != null) {
                parType =
                        new Class[2];
                argList =
                        new Object[2];
            } else {
                parType =
                        new Class[1];
                argList =
                        new Object[1];
            }

            // QueryManager type to instantiate
            parType[0] =
                    MetadataRepositoryImpl.class;

            // Adds the additional parameter (parameters HashMap)
            if (params != null) {
                parType[1] =
                        HashMap.class;
            }

            Constructor ct =
                    qmi.getConstructor(parType);

            MetadataRepositoryImpl rep =
                    this;
            argList[0] =
                    rep;
            if (params != null) {
                argList[1] =
                        params;
            }

            // Instantiates the new QueryManager
            retobj =
                    ct.newInstance(argList);


            //invoke method to get the required ontologies
            requiredOnts =
                    qmi.getMethod("getRequiredOntologies");
            uriList =
                    (List<URI>) requiredOnts.invoke(retobj);


            //get the ontology model
            OntModel ontologyModel =
                    ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM,
                    this._m);

            //get each required ontology
            for (int i =
                    0; i < uriList.size(); i++) {
                Ontology ontology =
                        ontologyModel.getOntology(uriList.get(i).toString());
                if (ontology != null) {

                    //invoke method to add the ontology
                    Class[] parAddOnt =
                            new Class[1];
                    Object[] ont =
                            new Object[1];

                    parAddOnt[0] =
                            Ontology.class;
                    ont[0] =
                            ontology;

                    addOnt =
                            qmi.getMethod("addOntologyRef", parAddOnt);
                    addOnt.invoke(retobj, ont);

                } else {
                    throw new OntologyNotInRepositoryException("Unable to find ontology " +
                            ontology.getLocalName().toString());
                }
            }

            /* ADDITIONAL PARAMETERS FOR THE QUERY MANAGER
             * Checks wheter it is necessary or not to process additional
             * parameters in the instantiated QueryManager
             */
            if (params != null) {

                // Checks if there is a session to bind in the QueryManager
                Session sess =
                        (Session) params.get("session");

                if (sess != null) {

                    Class[] parSessType =
                            new Class[1];
                    Object[] parSess =
                            new Object[1];

                    parSessType[0] =
                            Session.class;
                    parSess[0] =
                            sess;

                    Method bindToSession =
                            qmi.getMethod("bindToSession", parSessType);

                    bindToSession.invoke(retobj, parSess);

                }

                // Checks if there is a configuration file to read
                String confFilePath =
                        (String) params.get("confFilePath");

                if (confFilePath != null) {

                    Class[] parConfType =
                            new Class[1];
                    Object[] parConf =
                            new Object[1];

                    parConfType[0] =
                            String.class;
                    parConf[0] =
                            confFilePath;

                    Method readConfigurationFile =
                            qmi.getMethod("readConfigurationFile", parConfType);

                    readConfigurationFile.invoke(retobj, parConf);

                }

            }

            return (QueryManager) retobj;

        } catch (InstantiationException ex) {
            Logger.getLogger(MetadataRepositoryImpl.class.getName()).log(Level.SEVERE,
                    null, ex);
        } catch (IllegalAccessException ex) {
            Logger.getLogger(MetadataRepositoryImpl.class.getName()).log(Level.SEVERE,
                    null, ex);
        } catch (IllegalArgumentException ex) {
            Logger.getLogger(MetadataRepositoryImpl.class.getName()).log(Level.SEVERE,
                    null, ex);
        } catch (InvocationTargetException ex) {
            Logger.getLogger(MetadataRepositoryImpl.class.getName()).log(Level.SEVERE,
                    null, ex);
        } catch (NoSuchMethodException ex) {
            Logger.getLogger(MetadataRepositoryImpl.class.getName()).log(Level.SEVERE,
                    null, ex);
        } catch (SecurityException ex) {
            Logger.getLogger(MetadataRepositoryImpl.class.getName()).log(Level.SEVERE,
                    null, ex);
        } catch (ClassNotFoundException ex) {
            Logger.getLogger(MetadataRepositoryImpl.class.getName()).log(Level.SEVERE,
                    null, ex);
        }

        return (QueryManager) retobj;
    }

    /**
     * check if database schema exists
     * @return true or false value
     */
    boolean existsSchema() {
        boolean result =
                false;
        Connection conn =
                null;
        try {
            Class.forName("com.mysql.jdbc.Driver").newInstance();
            String url =
                    "jdbc:mysql://localhost/";
            conn =
                    (Connection) DriverManager.getConnection(url, M_DB_USER,
                    M_DB_PASSWD);
            if (conn != null) {
                java.sql.Statement stmt =
                        conn.createStatement();

                String modelName =
                        M_DB_URL.substring(M_DB_URL.lastIndexOf("/") + 1);
                java.sql.ResultSet rs =
                        null;
                try {

                    rs =
                            stmt.executeQuery("SELECT count(*) FROM information_schema.SCHEMATA where schema_name='" +
                            modelName + "'");
                } catch (Exception e) {

                    return false;
                }


                rs.next();
                int tmp =
                        rs.getInt(1);
                if (tmp == 1) {
                    result =
                            true;
                    System.out.println("SCHEMA '" + modelName + "' exists.");

                } else {
                    result =
                            false;
                    System.out.println("SCHEMA '" + modelName +
                            "' doesn't exist.");
                }
                stmt.close();
                conn.close();
            }
        } catch (SQLException ex) {
            ex.printStackTrace();
        } catch (ClassNotFoundException ex) {
            ex.printStackTrace();
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            return result;
        }

    }

    /**
     * Create database schema
     */
    public void createSchema() {
        //CREATE {DATABASE | SCHEMA} [IF NOT EXISTS] db_name
        Connection conn =
                null;
        try {
            Class.forName("com.mysql.jdbc.Driver").newInstance();
            String url =
                    "jdbc:mysql://localhost/";
            conn =
                    DriverManager.getConnection(url, M_DB_USER, M_DB_PASSWD);
            if (conn != null) {
                Statement stmt =
                        (Statement) conn.createStatement();
                String modelName =
                        M_DB_URL.substring(M_DB_URL.lastIndexOf("/") + 1);
                stmt.execute("CREATE SCHEMA IF NOT EXISTS " + modelName);
                System.out.println("SCHEMA succesfully created.");
                stmt.close();
                conn.close();
            }
        } catch (SQLException ex) {
            System.out.println(ex.getLocalizedMessage());
        } catch (ClassNotFoundException ex) {
            System.out.println(ex.getLocalizedMessage());
        } catch (Exception ex) {
            System.out.println(ex.getLocalizedMessage());
        }
    }
}
